home *** CD-ROM | disk | FTP | other *** search
- {*********************************************************}
- {* AADate.PAS *}
- {* Copyright (c) Julian M Bucknall 1993 - 1999 *}
- {* All rights reserved. *}
- {*********************************************************}
- {* Date arithmetic routines *}
- {*********************************************************}
-
- {Note: this unit is released as freeware. In other words, you are free
- to use this unit in your own applications, however I retain all
- copyright to the code. JMB}
-
- unit AADate;
-
- interface
-
- uses
- SysUtils,
- Classes;
-
- type
- TaaDate = longint;
- TaaDOW = (aaSunday, aaMonday, aaTuesday, aaWednesday,
- aaThursday, aaFriday, aaSaturday);
- TaaDateFormat = ( {Date string formats..}
- dfWindows, {..Windows defined}
- dfLotus, {..dd-Mmm-yyyy}
- dfLotusDOW, {..Ddd dd-Mmm-yyyy}
- dfDMY, {..dd/mm/yyyy}
- dfMDY, {..mm/dd/yyyy}
- dfYMD); {..yyyy/mm/dd}
-
- type
- TaaHolidayList = class
- private
- FList : TList;
- FWEDays : array [aaSunday..aaSaturday] of boolean;
- protected
- function hlGetHolidayCount : integer;
- function hlGetItem(aInx : integer) : TaaDate;
- function hlGetWeekend(aDOW : TaaDOW) : boolean;
- procedure hlSetWeekend(aDOW : TaaDOW; aValue : boolean);
-
- procedure hlStreamRead(aStream : TStream;
- var aBuffer; aCount : longint);
- procedure hlStreamWrite(aStream : TStream;
- var aBuffer; aCount : longint);
- public
- constructor Create;
- {-create instance, set weekend to Sat/Sun}
- destructor Destroy; override;
- {-free instance}
-
- procedure AddHoliday(aDate : TaaDate);
- {-add a new holiday}
- procedure Clear;
- {-clear all holidays, set weekend to Sat/Sun}
- procedure ClearBefore(aDate : TaaDate);
- {-clear all holidays before a certain date, leave weekend}
- procedure DeleteHoliday(aDate : TaaDate);
- {-delete a single holiday (no error if not there)}
-
- function IsBusinessDay(aDate : TaaDate) : boolean;
- {-return true if the day is not a weekend or holiday}
- function BusinessDaysDiff(aDate1, aDate2 : TaaDate) : integer;
- {-return the number of business days between aDate1 and
- aDate2; aDate1 <= aDate2, otherwise they're swapped over;
- count starts from aDate+1}
- function NextBusinessDay(aDate : TaaDate) : TaaDate;
- {-return the next business day from aDate}
- function PrevBusinessDay(aDate : TaaDate) : TaaDate;
- {-return the previous business day from aDate}
- function NearestBusinessDay(aDate : TaaDate;
- aSameMonth : boolean) : TaaDate;
- {-return the nearest business day to aDate; is aDate is a
- business day then it is returned, otherwise the next
- business day from aDate is returned; if aSameMonth is true,
- the date returned is forced to be in the same month as
- aDate.}
-
- procedure LoadFromStream(aStream : TStream);
- {-clear object, load data from stream}
- procedure StoreToStream(aStream : TStream);
- {-store objact data to stream}
-
- property Weekend[aDOW : TaaDOW] : boolean
- read hlGetWeekend write hlSetWeekend;
- {-for each day: weekend day if true, normal day if false}
- property HolidayCount : integer
- read hlGetHolidayCount;
- property Holidays[aInx : integer] : TaaDate
- read hlGetItem; default;
- {-holiday date list (sorted)}
- end;
-
-
- {--basic routines---}
- function aaYMDToDate(Y, M, D : integer) : TaaDate;
- procedure aaDateToYMD(aDate : TaaDate; var Y, M, D : integer);
- function aaIsLeapYear(Y : integer) : boolean;
- function aaDaysInMonth(Y, M : integer) : integer;
- function aaToday : TaaDate;
-
- {--conversion to/from other formats--}
- function aaDateToTDateTime(aDate : TaaDate) : TDateTime;
- function aaTDateTimeToDate(aDate : TDateTime) : TaaDate;
- function aaDateToStDate(aDate : TaaDate) : longint;
- function aaStDateToDate(aDate : longint) : TaaDate;
- function aaDateToGregDate(aDate : TaaDate) : longint;
- function aaGregDateToDate(aDate : longint) : TaaDate;
- procedure aaDateToISODate(aDate : TaaDate; var Y, W, D : integer);
- function aaISODateToDate(Y, W, D : integer) : TaaDate;
-
- {--month arithmetic--}
- function aaDateAddMonths(aDate : TaaDate; aMonths : integer;
- aStickyMonthEnds : boolean) : TaaDate;
- function aaDateDiffInMonths(aDate1, aDate2 : TaaDate;
- aStickyMonthEnds : boolean;
- var aDays : integer) : integer;
-
-
- {--day of week arithmetic---}
- function aaDayOfWeek(aDate : TaaDate) : TaaDOW;
- function aaIsDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : boolean;
- function aaNextDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : TaaDate;
- function aaPrevDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : TaaDate;
-
- {---validation---}
- function aaIsValidYMD(Y, M, D : integer) : boolean;
- function aaIsValidDate(aDate : TaaDate) : boolean;
-
- {---string representation---}
- function aaDateToStr(aDate : TaaDate; aFormat : TaaDateFormat) : string;
- function aaShortDayName(aDOW : TaaDOW) : string;
- function aaLongDayName(aDOW : TaaDOW) : string;
-
- implementation
-
- {$IFDEF Win32}
- uses
- Windows;
- {$ENDIF}
-
- type
- PFirstJanuarys = ^TFirstJanuarys;
- TFirstJanuarys = array [0..400] of TaaDate;
-
- PCumulativeDays = ^TCumulativeDays;
- TCumulativeDays = array [boolean, 0..12] of word;
-
- const
- DaysInMonth : array [1..13] of byte =
- (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0);
- DaysInFeb = 28;
- DaysInLeapFeb = 29;
- MaxDate = 146096; {date values range from 0 to 146096}
- MinYear = 1800; {year values range from 1800..}
- MaxYear = 2199; {..to 2199}
- MaxMonth = 4799; {month values range from 0 to 4799}
- DOW18000101 = aaWednesday; {1 Jan 1800 was a Wednesday}
- {$IFDEF Windows}
- MagicTDateTime = -657072;
- {$ELSE}
- MagicTDateTime = 36522;
- {$ENDIF}
- MagicStDate = -73049;
-
- var
- FirstJanuarys : PFirstJanuarys;
- CumulativeDays : PCumulativeDays;
- WindowsDateFormat : TaaDateFormat;
-
- {===Primitives=======================================================}
- function IsLeapYearPrim(Y : integer) : boolean;
- begin
- {assumes Y is valid}
- Result := ((Y mod 4) = 0) and
- (Y <> 1800) and (Y <> 1900) and (Y <> 2100);
- end;
- {--------}
- function DaysInMonthPrim(Y, M : integer) : integer;
- begin
- if (M = 2) and IsLeapYearPrim(Y) then
- Result := DaysInLeapFeb
- else
- Result := DaysInMonth[M];
- end;
- {====================================================================}
-
-
- {===Interfaced routines==============================================}
- function aaDateAddMonths(aDate : TaaDate; aMonths : integer;
- aStickyMonthEnds : boolean) : TaaDate;
- var
- Y, M, D : integer;
- DaysInM : integer;
- StickToMonthEnd : boolean;
- begin
- aaDateToYMD(aDate, Y, M, D);
- StickToMonthEnd := aStickyMonthEnds and (D = DaysInMonthPrim(Y, M));
- {calculate the month number from January 1800}
- M := (Y - MinYear) * 12 + pred(M) + aMonths;
- {if its out of range say so}
- if (M < 0) or (M > MaxMonth) then
- raise Exception.Create('aaDateAddMonths: calculated date in out of range');
- {calculate the new year and month}
- Y := (M div 12) + MinYear;
- M := succ(M mod 12);
- {check to see that the date is in range for the month}
- DaysInM := DaysInMonthPrim(Y, M);
- if StickToMonthEnd or (D > DaysInM) then
- D := DaysInM;
- Result := aaYMDToDate(Y, M, D);
- end;
- {--------}
- function aaDateDiffInMonths(aDate1, aDate2 : TaaDate;
- aStickyMonthEnds : boolean;
- var aDays : integer) : integer;
- var
- TempDate : TaaDate;
- Y1, M1, D1 : integer;
- Y2, M2, D2 : integer;
- Date1AtME : boolean;
- Date2AtME : boolean;
- begin
- {make sure that aDate1 is less than aDate2}
- if (aDate1 > aDate2) then begin
- TempDate := aDate1;
- aDate1 := aDate2;
- aDate2 := TempDate;
- end;
- {convert dates to YMD}
- aaDateToYMD(aDate1, Y1, M1, D1);
- aaDateToYMD(aDate2, Y2, M2, D2);
- {make first approximation to answer}
- Result := ((Y2 - Y1) * 12) + (M2 - M1);
- {if both day numbers are less then 28, we don't have to worry about
- any month end calculations}
- if (D1 < 28) and (D2 < 28) then begin
- {if the first day is less than or equal to the second, then the
- day count is just the difference}
- if (D1 <= D2) then
- aDays := D2 - D1
- {otherwise, the month count is one too many, then we have to count
- the number of days from Y2/(M2-1)/D1 to Y2/M2/D2; the former date
- being Result whole months from aDate1}
- else begin
- dec(Result);
- dec(M2);
- if (M2 = 0) then begin
- M2 := 12;
- dec(Y2);
- end;
- if (D1 > DaysInMonthPrim(Y2, M2)) then
- D1 := DaysInMonthPrim(Y2, M2);
- aDays := aDate2 - aaYMDToDate(Y2, M2, D1);
- end;
- Exit;
- end;
- {if we reach this point, one or both of the dates might be at a
- month end, so *beware*}
- Date1AtME := D1 = DaysInMonthPrim(Y1, M1);
- Date2AtME := D2 = DaysInMonthPrim(Y2, M2);
- {the easiest case is both days are at month ends and we want sticky
- month ends: we're done after setting aDays to zero}
- if aStickyMonthEnds and Date1AtME and Date2AtME then begin
- aDays := 0;
- Exit;
- end;
- {the next easiest cases all use sticky month ends}
- if aStickyMonthEnds then begin
- {if the first date is at a month end (the second won't be) then
- the number of months is one too many, and the number of days is
- equal to the second day value}
- if Date1AtME then begin {note: Date2AtME = false}
- dec(Result);
- aDays := D2;
- Exit;
- end;
- {if the second date is at a month end (the first won't be) then
- the number of months is correct, and the number of days is
- equal to the second day value minus the first, or zero if this
- is negative}
- if Date2AtME then begin {note: Date1AtME = false}
- if D2 >= D1 then
- aDays := D2 - D1
- else
- aDays := 0;
- Exit;
- end;
- end;
- {if the second day number is greater or equal to the first, the
- number of days is the difference; the number of months is correct}
- if (D2 >= D1) then begin
- aDays := D2 - D1;
- Exit;
- end;
- {otherwise, the number of months is one too many, and the number of
- days is that from Y2/(M2-1)/D1 to Y2/M2/D2}
- dec(Result);
- dec(M2);
- if (M2 = 0) then begin
- M2 := 12;
- dec(Y2);
- end;
- if (D1 > DaysInMonthPrim(Y2, M2)) then
- D1 := DaysInMonthPrim(Y2, M2);
- aDays := aDate2 - aaYMDToDate(Y2, M2, D1);
- end;
- {--------}
- function aaDateToGregDate(aDate : TaaDate) : longint;
- var
- Y, M, D : integer;
- begin
- aaDateToYMD(aDate, Y, M, D);
- Result := (((longint(Y) * 100) + M) * 100) + D;
- end;
- {--------}
- procedure aaDateToISODate(aDate : TaaDate; var Y, W, D : integer);
- var
- xY, xM, xD : integer;
- FirstWeek : TaaDate;
- FirstWeekNext : TaaDate;
- begin
- {Notes: an ISO date is defined by the year, week number and day
- within the week. A week starts on a Monday and this is day 1
- (hence Sunday is day 7). The first week of the year is the
- one that contains the first Thursday of the year.
- Clever Stuff Dept: Week 1 of year Y starts on the first
- Monday after 28 December, (Y-1); of course, *that* is 4 days
- before 1 January, Y}
- aaDateToYMD(aDate, xY, xM, xD);
- FirstWeek := aaNextDayOfWeek(FirstJanuarys^[xY-MinYear]-4, aaMonday);
- if (aDate < FirstWeek) then begin
- dec(xY);
- FirstWeek := aaNextDayOfWeek(FirstJanuarys^[xY-MinYear]-4, aaMonday);
- end
- else begin
- FirstWeekNext := aaNextDayOfWeek(FirstJanuarys^[xY-MinYear+1]-4, aaMonday);
- if (aDate >= FirstWeekNext) then begin
- inc(xY);
- FirstWeek := FirstWeekNext;
- end;
- end;
- Y := xY;
- W := succ((aDate - FirstWeek) div 7);
- D := succ((aDate - FirstWeek) mod 7);
- end;
- {--------}
- function aaDateToStr(aDate : TaaDate; aFormat : TaaDateFormat) : string;
- var
- Y, M, D : integer;
- DOW : TaaDOW;
- begin
- aaDateToYMD(aDate, Y, M, D);
- if aFormat = dfWindows then
- aFormat := WindowsDateFormat;
- case aFormat of
- dfLotus :
- begin
- Result := Format('%2d-%s-%d',
- [D, ShortMonthNames[M], Y]);
- end;
- dfLotusDOW :
- begin
- DOW := aaDayOfWeek(aDate);
- Result := Format('%s %2d-%s-%d',
- [ShortDayNames[succ(ord(DOW))],
- D, ShortMonthNames[M], Y]);
- end;
- dfDMY :
- begin
- Result := Format('%2d-%2d-%d', [D, M, Y]);
- if Result[4] = ' ' then
- Result[4] := '0';
- Result[3] := DateSeparator;
- Result[6] := DateSeparator;
- end;
- dfMDY :
- begin
- Result := Format('%2d-%2d-%d', [M, D, Y]);
- if Result[4] = ' ' then
- Result[4] := '0';
- Result[3] := DateSeparator;
- Result[6] := DateSeparator;
- end;
- dfYMD :
- begin
- Result := Format('%d-%2d-%2d', [Y, M, D]);
- if Result[6] = ' ' then
- Result[6] := '0';
- if Result[9] = ' ' then
- Result[9] := '0';
- Result[5] := DateSeparator;
- Result[8] := DateSeparator;
- end;
- else
- Result := '';
- end;
- end;
- {--------}
- function aaDateToStDate(aDate : TaaDate) : longint;
- begin
- if (aDate < 0) or (aDate > MaxDate) then
- raise Exception.Create('aaDateToStDate: invalid date');
- Result := aDate - MagicStDate;
- end;
- {--------}
- function aaDateToTDateTime(aDate : TaaDate) : TDateTime;
- begin
- if (aDate < 0) or (aDate > MaxDate) then
- raise Exception.Create('aaDateToTDateTime: invalid date');
- Result := aDate - MagicTDateTime;
- end;
- {--------}
- procedure aaDateToYMD(aDate : TaaDate; var Y, M, D : integer);
- {.$DEFINE SequentialSearch}
- {.$DEFINE BinarySearch}
- {$DEFINE InterpolationSearch}
- var
- Inx : integer;
- IsLeap : boolean;
- {$IFDEF SequentialSearch}
- FoundIt : boolean;
- {$ENDIF}
- {$IFDEF BinarySearch}
- FoundIt : boolean;
- L, Mid, R : integer;
- {$ENDIF}
- begin
- if (aDate < 0) or (aDate > MaxDate) then
- raise Exception.Create('aaDateToYMD: invalid date');
- {$IFDEF SequentialSearch}
- FoundIt := true;
- for Inx := 0 to 400 do
- if (aDate < FirstJanuarys^[Inx]) then begin
- FoundIt := true;
- Break;
- end;
- if FoundIt then
- dec(Inx)
- else
- Inx := 399;
- {$ENDIF}
- {$IFDEF BinarySearch}
- FoundIt := false;
- L := 0;
- R := 400;
- while (L <= R) do begin
- Mid := (L + R) div 2;
- if (aDate < FirstJanuarys^[Mid]) then
- R := pred(Mid)
- else if (aDate > FirstJanuarys^[Mid]) then
- L := succ(Mid)
- else {equal} begin
- FoundIt := true;
- Break;
- end;
- end;
- if FoundIt then
- Inx := Mid
- else
- Inx := L-1;
- {$ENDIF}
- {$IFDEF InterpolationSearch}
- {use interpolation search to calculate 1 January, & hence the year}
- Inx := aDate div 365;
- if (aDate < FirstJanuarys^[Inx]) then
- dec(Inx);
- {$ENDIF}
- Y := MinYear + Inx;
- IsLeap := ((Inx mod 4) = 0) and
- (Inx <> 0) and (Inx <> 100) and (Inx <> 300);
- {use interpolation search to calculate the month}
- aDate := aDate - FirstJanuarys^[Inx];
- Inx := (aDate div 32) + 1;
- if (aDate < CumulativeDays^[IsLeap, Inx]) then
- dec(Inx);
- M := succ(Inx);
- {calculate the day}
- D := aDate - CumulativeDays^[IsLeap, Inx] + 1;
- end;
- {--------}
- function aaDayOfWeek(aDate : TaaDate) : TaaDOW;
- begin
- if (aDate < 0) or (aDate > MaxDate) then
- raise Exception.Create('aaDayOfWeek: invalid date');
- Result := TaaDOW((aDate + ord(DOW18000101)) mod 7);
- end;
- {--------}
- function aaDaysInMonth(Y, M : integer) : integer;
- begin
- if (Y < MinYear) or (Y > MaxYear) or
- (M < 1) or (M > 12) then
- raise Exception.Create('aaDaysInMonth: invalid year and/or month');
- if (M = 2) and IsLeapYearPrim(Y) then
- Result := DaysInLeapFeb
- else
- Result := DaysInMonth[M];
- end;
- {--------}
- function aaGregDateToDate(aDate : longint) : TaaDate;
- var
- Y, M, D : integer;
- begin
- Y := aDate div 10000;
- M := (aDate mod 10000) div 100;
- D := aDate mod 100;
- Result := aaYMDToDate(Y, M, D);
- end;
- {--------}
- function aaIsDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : boolean;
- begin
- Result := aaDayOfWeek(aDate) = aDOW;
- end;
- {--------}
- function aaIsLeapYear(Y : integer) : boolean;
- begin
- if (Y < MinYear) or (Y > MaxYear) then
- raise Exception.Create('aaIsLeapYear: invalid year, should be 1800-2199');
- Result := ((Y mod 4) = 0) and
- (Y <> 1800) and (Y <> 1900) and (Y <> 2100);
- end;
- {--------}
- function aaISODateToDate(Y, W, D : integer) : TaaDate;
- var
- FirstWeek : TaaDate;
- begin
- {Notes: an ISO date is defined by the year, week number and day
- within the week. A week starts on a Monday and this is day 1
- (hence Sunday is day 7). The first week of the year is the
- one that contains the first Thursday of the year.
- Clever Stuff Dept: Week 1 of year Y starts on the first
- Monday after 28 December, (Y-1); of course, *that* is 4 days
- before 1 January, Y}
- if (Y < MinYear) or (Y > MaxYear) then
- raise Exception.Create('aaISODateToDate: invalid year, should be 1800-2199');
- if (W < 1) or (W > 53) then
- raise Exception.Create('aaISODateToDate: invalid week, should be 1-53');
- if (D < 1) or (D > 7) then
- raise Exception.Create('aaISODateToDate: invalid day, should be 1 (Monday) to 7 (Sunday)');
- FirstWeek := aaNextDayOfWeek(FirstJanuarys^[Y-MinYear]-4, aaMonday);
- Result := FirstWeek + ((W - 1) * 7) + (D - 1);
- end;
- {--------}
- function aaIsValidDate(aDate : TaaDate) : boolean;
- begin
- Result := (0 <= aDate) and (aDate <= MaxDate);
- end;
- {--------}
- function aaIsValidYMD(Y, M, D : integer) : boolean;
- begin
- Result := false;
- {easy checks}
- if (Y < MinYear) or (Y > MaxYear) then Exit;
- if (M < 1) or (M > 12) then Exit;
- if (D < 1) then Exit;
- {full check on day}
- if (D > 28) then begin
- {if February..}
- if (M = 2) then begin
- {if leap year..}
- if ((Y mod 4) = 0) and
- (Y <> 1800) and (Y <> 1900) and (Y <> 2100) then begin
- if (D > DaysInLeapFeb) then Exit;
- end
- else
- if (D > DaysInFeb) then Exit;
- end
- else
- if (D > DaysInMonth[M]) then Exit;
- end;
- {otherwise it's OK}
- Result := true;
- end;
- {--------}
- function aaLongDayName(aDOW : TaaDOW) : string;
- begin
- Result := LongDayNames[succ(ord(aDOW))];
- end;
- {--------}
- function aaNextDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : TaaDate;
- var
- ThisDOW : TaaDOW;
- begin
- ThisDOW := aaDayOfWeek(aDate);
- Result := aDate + (ord(aDOW) - ord(ThisDOW));
- if (ThisDOW >= aDOW) then
- inc(Result, 7);
- if (Result < 0) or (Result > MaxDate) then
- raise Exception.Create('aaNextDayOfWeek: calculated date out of range');
- end;
- {--------}
- function aaPrevDayOfWeek(aDate : TaaDate; aDOW : TaaDOW) : TaaDate;
- var
- ThisDOW : TaaDOW;
- begin
- ThisDOW := aaDayOfWeek(aDate);
- Result := aDate + (ord(aDOW) - ord(ThisDOW));
- if (ThisDOW <= aDOW) then
- dec(Result, 7);
- if (Result < 0) or (Result > MaxDate) then
- raise Exception.Create('aaPrevDayOfWeek: calculated date out of range');
- end;
- {--------}
- function aaShortDayName(aDOW : TaaDOW) : string;
- begin
- Result := ShortDayNames[succ(ord(aDOW))];
- end;
- {--------}
- function aaStDateToDate(aDate : longint) : TaaDate;
- begin
- Result := aDate + MagicStDate;
- if (Result < 0) or (Result > MaxDate) then
- raise Exception.Create('aaStDateToDate: invalid date');
- end;
- {--------}
- function aaTDateTimeToDate(aDate : TDateTime) : TaaDate;
- begin
- Result := Trunc(aDate) + MagicTDateTime;
- if (Result < 0) or (Result > MaxDate) then
- raise Exception.Create('aaTDateTimeToDate: invalid date');
- end;
- {--------}
- function aaToday : TaaDate;
- {$IFDEF Windows}
- assembler;
- asm
- mov ah, 2Ah {get date from DOS}
- int 21h
- push cx {push year}
- xor ax, ax
- mov al, dh
- push ax {push month}
- mov al, dl
- push ax {push day}
- call aaYMDToDate {convert}
- end;
- {$ELSE}
- var
- SystemTime: TSystemTime;
- begin
- GetLocalTime(SystemTime);
- with SystemTime do
- Result := aaYMDToDate(wYear, wMonth, wDay);
- end;
- {$ENDIF}
- {--------}
- function aaYMDToDate(Y, M, D : integer) : TaaDate;
- var
- IsLeap : boolean;
- begin
- if not aaIsValidYMD(Y, M, D) then
- raise Exception.Create(
- Format('aaYMDToDate: invalid year %d, month %d, day %d',
- [Y, M, D]));
- IsLeap := ((Y mod 4) = 0) and
- (Y <> 1800) and (Y <> 1900) and (Y <> 2100);
- Result := FirstJanuarys^[Y-MinYear] +
- CumulativeDays^[IsLeap, pred(M)] +
- pred(D);
- end;
- {====================================================================}
-
-
- {===TaaHolidayList===================================================}
- constructor TaaHolidayList.Create;
- begin
- inherited Create;
- FList := TList.Create;
- FWEDays[aaSaturday] := true;
- FWEDays[aaSunday] := true;
- end;
- {--------}
- destructor TaaHolidayList.Destroy;
- begin
- FList.Free;
- inherited Create;
- end;
- {--------}
- procedure TaaHolidayList.AddHoliday(aDate : TaaDate);
- var
- L, R, M : integer;
- MidDate : TaaDate;
- begin
- if (FList.Count = 0) then
- FList.Add(pointer(aDate))
- else begin
- {find aDate in the list by binary search, if found, exit, if not
- insert at the correct spot}
- L := 0;
- R := pred(FList.Count);
- while L <= R do begin
- M := (L + R) div 2;
- MidDate := TaaDate(FList[M]);
- if (aDate < MidDate) then
- R := pred(M)
- else if (aDate > MidDate) then
- L := succ(M)
- else {they're equal}
- Exit;
- end;
- FList.Insert(L, pointer(aDate));
- end;
- end;
- {--------}
- function TaaHolidayList.BusinessDaysDiff(aDate1, aDate2 : TaaDate) : integer;
- var
- TempDate : TaaDate;
- begin
- {make sure that aDate1 is less than aDate2}
- if (aDate1 > aDate2) then begin
- TempDate := aDate1;
- aDate1 := aDate2;
- aDate2 := TempDate;
- end;
- {count the business days from aDate1 to aDate2 inclusive}
- Result := 0;
- inc(aDate1);
- while (aDate1 <= aDate2) do begin
- if IsBusinessDay(aDate1) then
- inc(Result);
- inc(aDate1);
- end;
- end;
- {--------}
- procedure TaaHolidayList.Clear;
- begin
- FList.Clear;
- FillChar(FWEDays, sizeof(FWEDays), 0);
- FWEDays[aaSaturday] := true;
- FWEDays[aaSunday] := true;
- end;
- {--------}
- procedure TaaHolidayList.ClearBefore(aDate : TaaDate);
- var
- L, R, M : integer;
- MidDate : TaaDate;
- PointerList : PPointerList;
- begin
- if (FList.Count > 0) then begin
- {find aDate in the list by binary search}
- L := 0;
- R := pred(FList.Count);
- while L <= R do begin
- M := (L + R) div 2;
- MidDate := TaaDate(FList[M]);
- if (aDate < MidDate) then
- R := pred(M)
- else if (aDate > MidDate) then
- L := succ(M)
- else {they're equal} begin
- L := M;
- Break;
- end;
- end;
- {we now have to delete all entries prior to L}
- if (L > 0) then begin
- PointerList := FList.List;
- Move(PointerList^[L],
- PointerList^[0],
- (FList.Count - L) * sizeof(pointer));
- FList.Count := FList.Count - L;
- end;
- end;
- end;
- {--------}
- procedure TaaHolidayList.DeleteHoliday(aDate : TaaDate);
- var
- L, R, M : integer;
- MidDate : TaaDate;
- begin
- if (FList.Count > 0) then begin
- {find aDate in the list by binary search and delete it}
- L := 0;
- R := pred(FList.Count);
- while L <= R do begin
- M := (L + R) div 2;
- MidDate := TaaDate(FList[M]);
- if (aDate < MidDate) then
- R := pred(M)
- else if (aDate > MidDate) then
- L := succ(M)
- else {they're equal} begin
- FList.Delete(M);
- Exit;
- end;
- end;
- end;
- end;
- {--------}
- function TaaHolidayList.hlGetHolidayCount : integer;
- begin
- Result := FList.Count;
- end;
- {--------}
- function TaaHolidayList.hlGetItem(aInx : integer) : TaaDate;
- begin
- Result := TaaDate(FList[aInx]);
- end;
- {--------}
- function TaaHolidayList.hlGetWeekend(aDOW : TaaDOW) : boolean;
- begin
- Result := FWEDays[aDOW];
- end;
- {--------}
- procedure TaaHolidayList.hlSetWeekend(aDOW : TaaDOW; aValue : boolean);
- begin
- FWEDays[aDOW] := aValue;
- end;
- {--------}
- procedure TaaHolidayList.hlStreamRead(aStream : TStream;
- var aBuffer; aCount : longint);
- var
- BytesRead : longint;
- begin
- BytesRead := aStream.Read(aBuffer, aCount);
- if (BytesRead <> aCount) then
- raise Exception.Create('hlStreamRead: not enough bytes read');
- end;
- {--------}
- procedure TaaHolidayList.hlStreamWrite(aStream : TStream;
- var aBuffer; aCount : longint);
- var
- BytesWrit : longint;
- begin
- BytesWrit := aStream.Write(aBuffer, aCount);
- if (BytesWrit <> aCount) then
- raise Exception.Create('hlStreamWrite: not enough bytes written');
- end;
- {--------}
- function TaaHolidayList.IsBusinessDay(aDate : TaaDate) : boolean;
- var
- DOW : TaaDOW;
- L, R, M : integer;
- MidDate : TaaDate;
- begin
- Result := true;
- {first calculate the day of the week and check whether it's a
- weekend day}
- DOW := aaDayOfWeek(aDate);
- if FWEDays[DOW] then
- Result := false
- {otherwise, try to find the date in the holiday list}
- else if (FList.Count <> 0) then begin
- L := 0;
- R := pred(FList.Count);
- while L <= R do begin
- M := (L + R) div 2;
- MidDate := TaaDate(FList[M]);
- if (aDate < MidDate) then
- R := pred(M)
- else if (aDate > MidDate) then
- L := succ(M)
- else {they're equal} begin
- Result := false;
- Exit;
- end;
- end;
- end;
- end;
- {--------}
- procedure TaaHolidayList.LoadFromStream(aStream : TStream);
- var
- Count : integer;
- PointerList : PPointerList;
- begin
- hlStreamRead(aStream, FWEDays, sizeof(FWEDays));
- hlStreamRead(aStream, Count, sizeof(Count));
- FList.Count := Count;
- PointerList := FList.List;
- hlStreamRead(aStream, PointerList^, Count * sizeof(pointer));
- end;
- {--------}
- function TaaHolidayList.NearestBusinessDay(aDate : TaaDate;
- aSameMonth : boolean) : TaaDate;
- var
- Y1, M1, D1 : integer;
- Y2, M2, D2 : integer;
- begin
- if IsBusinessDay(aDate) then
- Result := aDate
- else begin
- Result := succ(aDate);
- while not IsBusinessDay(Result) do
- Result := succ(Result);
- if aSameMonth then begin
- aaDateToYMD(aDate, Y1, M1, D1);
- aaDateToYMD(Result, Y2, M2, D2);
- if (M1 <> M2) then begin
- Result := pred(Result);
- while not IsBusinessDay(Result) do
- Result := pred(Result);
- end;
- end;
- end;
- end;
- {--------}
- function TaaHolidayList.NextBusinessDay(aDate : TaaDate) : TaaDate;
- begin
- Result := succ(aDate);
- while not IsBusinessDay(Result) do
- Result := succ(Result);
- end;
- {--------}
- function TaaHolidayList.PrevBusinessDay(aDate : TaaDate) : TaaDate;
- begin
- Result := pred(aDate);
- while not IsBusinessDay(Result) do
- Result := pred(Result);
- end;
- {--------}
- procedure TaaHolidayList.StoreToStream(aStream : TStream);
- var
- Count : integer;
- PointerList : PPointerList;
- begin
- hlStreamWrite(aStream, FWEDays, sizeof(FWEDays));
- Count := FList.Count;
- hlStreamWrite(aStream, Count, sizeof(Count));
- PointerList := FList.List;
- hlStreamWrite(aStream, PointerList^, Count * sizeof(pointer));
- end;
- {====================================================================}
-
-
- {===Initialization and finalization==================================}
- procedure InitFirstJans;
- var
- NextValue : longint;
- Year : integer;
- begin
- {allocate the memory}
- New(FirstJanuarys);
- {initialize the values}
- NextValue := 0;
- for Year := MinYear to MaxYear do begin
- FirstJanuarys^[Year-MinYear] := NextValue;
- if aaIsLeapYear(Year) then
- inc(NextValue, 366)
- else
- inc(NextValue, 365)
- end;
- FirstJanuarys^[400] := NextValue;
- end;
- {--------}
- procedure InitCumulativeDays;
- var
- NextValue : longint;
- Month : integer;
- begin
- {allocate the memory}
- New(CumulativeDays);
- {initialize the non-leap year values}
- NextValue := 0;
- for Month := 1 to 12 do begin
- CumulativeDays^[false, pred(Month)] := NextValue;
- inc(NextValue, DaysInMonth[Month]);
- end;
- CumulativeDays^[false, 12] := NextValue;
- {initialize the non-leap year values}
- NextValue := 0;
- for Month := 1 to 12 do begin
- CumulativeDays^[true, pred(Month)] := NextValue;
- if (Month = 2) then
- inc(NextValue, DaysInLeapFeb)
- else
- inc(NextValue, DaysInMonth[Month]);
- end;
- CumulativeDays^[true, 12] := NextValue;
- end;
- {--------}
- procedure CalcWindowsDateFormat;
- var
- i : integer;
- begin
- {simple calculation}
- for i := 1 to length(ShortDateFormat) do
- case ShortDateFormat[i] of
- 'd' : begin
- WindowsDateFormat := dfDMY;
- Exit;
- end;
- 'm' : begin
- WindowsDateFormat := dfMDY;
- Exit;
- end;
- 'y' : begin
- WindowsDateFormat := dfYMD;
- Exit;
- end;
- end;
- WindowsDateFormat := dfDMY;
- end;
- {--------}
- procedure FinalizeUnit; far;
- begin
- if (FirstJanuarys <> nil) then
- Dispose(FirstJanuarys);
- if (CumulativeDays <> nil) then
- Dispose(CumulativeDays);
- end;
- {====================================================================}
-
- initialization
- FirstJanuarys := nil;
- CumulativeDays := nil;
- InitFirstJans;
- InitCumulativeDays;
- CalcWindowsDateFormat;
- {$IFDEF Windows}
- AddExitProc(FinalizeUnit);
- {$ENDIF}
-
- {$IFDEF Win32}
- finalization
- FinalizeUnit;
- {$ENDIF}
-
- end.
-